package org.xiaoxin.onecoupon.merchant.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.User;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.xiaoxin.onecoupon.framework.exception.ClientException;
import org.xiaoxin.onecoupon.merchant.admin.common.constant.MerchantAdminRedisConstant;
import org.xiaoxin.onecoupon.merchant.admin.common.context.UserContext;
import org.xiaoxin.onecoupon.merchant.admin.common.enums.CouponTemplateStatusEnum;
import org.xiaoxin.onecoupon.merchant.admin.dao.entity.CouponTemplateDO;
import org.xiaoxin.onecoupon.merchant.admin.dao.mapper.CouponTemplateMapper;
import org.xiaoxin.onecoupon.merchant.admin.dto.req.CouponTemplateNumberReqDTO;
import org.xiaoxin.onecoupon.merchant.admin.dto.req.CouponTemplatePageQueryReqDTO;
import org.xiaoxin.onecoupon.merchant.admin.dto.req.CouponTemplateSaveReqDTO;
import org.xiaoxin.onecoupon.merchant.admin.dto.resp.CouponTemplatePageQueryRespDTO;
import org.xiaoxin.onecoupon.merchant.admin.dto.resp.CouponTemplateQueryRespDTO;
import org.xiaoxin.onecoupon.merchant.admin.service.CouponTemplateService;
import org.xiaoxin.onecoupon.merchant.admin.service.basic.chain.MerchantAdminChainContext;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.xiaoxin.onecoupon.merchant.admin.common.enums.ChainBizMarkEnum.MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY;

/**
 * @author xiaoxin
 * 2025/4/9
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CouponTemplateServiceImpl extends ServiceImpl<CouponTemplateMapper, CouponTemplateDO> implements CouponTemplateService {
   private final MerchantAdminChainContext merchantAdminChainContext;
   private  final CouponTemplateMapper couponTemplateMapper;
   private final StringRedisTemplate stringRedisTemplate;

   private final RedissonClient redissonClient;
   private final RocketMQTemplate rocketMQTemplate;
   private final ConfigurableEnvironment configurableEnvironment;
   private  final RBloomFilter<String> couponTemplateQueryBloomFilter;


   /**
    * success：方法执行成功后的日志模版。
    * type：操作日志的类型，比如：订单类型、商品类型。
    * bizNo：日志绑定的业务标识，需要是我们优惠券模板的 ID，但是目前拿不到，放一个占位符。
    * extra：日志的额外信息
    */
   @LogRecord(
           success = """
                    创建优惠券：{{#couponTemplateSaveReqDTO.name}}， \
                    优惠对象：{COMMON_ENUM_PARSE{'DiscountTargetEnum' + '_' + #couponTemplateSaveReqDTO.target}}， \
                    优惠类型：{COMMON_ENUM_PARSE{'DiscountTypeEnum' + '_' + #couponTemplateSaveReqDTO.type}}， \
                    库存数量：{{#couponTemplateSaveReqDTO.stock}}， \
                    优惠商品编码：{{#couponTemplateSaveReqDTO.goods}}， \
                    有效期开始时间：{{#couponTemplateSaveReqDTO.validStartTime}}， \
                    有效期结束时间：{{#couponTemplateSaveReqDTO.validEndTime}}， \
                    领取规则：{{#couponTemplateSaveReqDTO.receiveRule}}， \
                    消耗规则：{{#couponTemplateSaveReqDTO.consumeRule}};
                    """,
           type = "CouponTemplate",
           bizNo = "{{#bizNo}}",
           extra = "{{#couponTemplateSaveReqDTO.toString()}}"
   )
   @Override
   public void createCouponTemplate(CouponTemplateSaveReqDTO couponTemplateSaveReqDTO) {
       //通过责任链验证请求参数是否正确
       merchantAdminChainContext.handler(MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY.name(),couponTemplateSaveReqDTO);
       //新增优惠券模板信息到数据库
       CouponTemplateDO couponTemplateDO = BeanUtil.toBean(couponTemplateSaveReqDTO, CouponTemplateDO.class);
       couponTemplateDO.setShopNumber(UserContext.getShopNumber());
       couponTemplateDO.setStatus(CouponTemplateStatusEnum.ACTIVE.getStatus());
       //这里使用的MyBatis-Plus
       //MyBatis-Plus 提供了一个 BaseMapper 接口，它是一个通用的 Mapper 接口，用于简化 CRUD 操作。
       // 当你继承 BaseMapper 时，MyBatis-Plus 会自动识别这个接口，并为其生成对应的 Mapper Bean，而无需手动添加 @Mapper 注解。
       couponTemplateMapper.insert(couponTemplateDO);


       // 因为模板 ID 是运行中生成的，@LogRecord 默认拿不到，所以我们需要手动设置
       LogRecordContext.putVariable("bizNo", couponTemplateDO.getId());

       // 缓存预热：通过将数据库的记录序列化成 JSON 字符串放入 Redis 缓存
       CouponTemplateQueryRespDTO actualRespDTO = BeanUtil.toBean(couponTemplateDO, CouponTemplateQueryRespDTO.class);
       Map<String, Object> cacheTargetMap = BeanUtil.beanToMap(actualRespDTO, false, true);
       Map<String, String> actualCacheTargetMap = cacheTargetMap.entrySet().stream()
               .collect(Collectors.toMap(
                       Map.Entry::getKey,
                       entry -> entry.getValue() != null ? entry.getValue().toString() : ""
               ));
       String couponTemplateCacheKey = String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY, couponTemplateDO.getId());

       // 通过 LUA 脚本执行设置 Hash 数据以及设置过期时间
       String luaScript = "redis.call('HMSET', KEYS[1], unpack(ARGV, 1, #ARGV - 1)) " +
               "redis.call('EXPIREAT', KEYS[1], ARGV[#ARGV])";

       List<String> keys = Collections.singletonList(couponTemplateCacheKey);
       List<String> args = new ArrayList<>(actualCacheTargetMap.size() * 2 + 1);
       actualCacheTargetMap.forEach((key, value) -> {
           args.add(key);
           args.add(value);
       });

       // 优惠券活动过期时间转换为秒级别的 Unix 时间戳
       args.add(String.valueOf(couponTemplateDO.getValidEndTime().getTime() / 1000));

       // 执行 LUA 脚本
       stringRedisTemplate.execute(
               new DefaultRedisScript<>(luaScript, Long.class),
               keys,
               args.toArray()
       );
       // 使用 RocketMQ5.x 发送任意时间延时消息
       // 定义 Topic
       String couponTemplateDelayCloseTopic = "one-coupon_merchant-admin-service_coupon-template-delay_topic${unique-name:}";
       // 通过 Spring 上下文解析占位符，也就是把咱们 VM 参数里的 unique-name 替换到字符串中
       couponTemplateDelayCloseTopic = configurableEnvironment.resolvePlaceholders(couponTemplateDelayCloseTopic);
       //定义消息体
       JSONObject messageBody = new JSONObject();
       messageBody.put("couponTemplateId",couponTemplateDO.getId());
       messageBody.put("shopNumber", UserContext.getShopNumber());
       //设置消息的送达时间，毫秒级 Unix 时间戳
       long deliverTimeStamp = couponTemplateDO.getValidEndTime().getTime();
       //构建消息体
       String messageKeys = UUID.randomUUID().toString();
       Message<JSONObject> message = MessageBuilder
               .withPayload(messageBody)
               .setHeader(MessageConst.PROPERTY_KEYS, messageKeys)
               .build();
       //执行 RocketMQ5.x 消息队列发送&异常处理逻辑
       SendResult sendResult;
       try {
           sendResult = rocketMQTemplate.syncSendDeliverTimeMills(couponTemplateDelayCloseTopic, message, deliverTimeStamp);
           log.info("[生产者] 优惠券模板延时关闭 - 发送结果：{}，消息ID：{}，消息Keys：{}", sendResult.getSendStatus(), sendResult.getMsgId(), messageKeys);
       } catch (Exception ex) {
           log.error("[生产者] 优惠券模板延时关闭 - 消息发送失败，消息体：{}", couponTemplateDO.getId(), ex);
       }
       //添加优惠劵模板id到布隆过滤器中
       couponTemplateQueryBloomFilter.add(String.valueOf(couponTemplateDO.getId()));


   }

   @LogRecord(
           success = "结束优惠卷",
           type = "CouponTemplate",
           bizNo = "{{#couponTemplateId}}"
   )
   public  void terminateCouponTemplate(String couponTemplateId){
       LambdaQueryWrapper<CouponTemplateDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateDO.class)
               .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
               .eq(CouponTemplateDO::getId, couponTemplateId);
       CouponTemplateDO couponTemplateDO = couponTemplateMapper.selectOne(queryWrapper);
       // 验证优惠券模板是否正常
       if (ObjectUtil.notEqual(couponTemplateDO.getStatus(), CouponTemplateStatusEnum.ACTIVE.getStatus())) {
           throw new ClientException("优惠券模板已结束");
       }
       if (couponTemplateDO == null){
           throw new ClientException("优惠券模板异常，请检查操作是否正确...");
       }
       // 记录优惠券模板修改前数据
       LogRecordContext.putVariable("originalData",JSON.toJSONString(couponTemplateId));
       //修改优惠劵状态
       CouponTemplateDO updateCouponTemplateDo = CouponTemplateDO.builder()
               .status(CouponTemplateStatusEnum.ENDED.getStatus())
               .build();
       LambdaUpdateWrapper<CouponTemplateDO> updateWrapper = Wrappers.lambdaUpdate(CouponTemplateDO.class)
               .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
               .eq(CouponTemplateDO::getId, couponTemplateDO.getId());
       couponTemplateMapper.update(updateCouponTemplateDo,updateWrapper);
       // 修改优惠券模板缓存状态为结束状态
       String couponTemplateCacheKey = String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY, couponTemplateId);
       stringRedisTemplate.opsForHash().put(couponTemplateCacheKey, "status", String.valueOf(CouponTemplateStatusEnum.ENDED.getStatus()));

   }
    /**
     * 增加优惠卷发行量
     *
     */
    @LogRecord(
            success = "增加发行量：{{#requestParam.number}}",
            type = "CouponTemplate",
            bizNo = "{{#requestParam.couponTemplateId}}"
    )
    public void increaseNumberCouponTemplate(CouponTemplateNumberReqDTO requestParam){
        //1.验证是否发生数据越权
        LambdaQueryWrapper<CouponTemplateDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateDO.class)
                .eq(CouponTemplateDO::getShopNumber,UserContext.getShopNumber())
                .eq(CouponTemplateDO::getId,requestParam.getCouponTemplateId());
        CouponTemplateDO couponTemplateDO = couponTemplateMapper.selectOne(queryWrapper);
        //2.验证优惠券模板是否正常
        if (couponTemplateDO == null){
            throw new ClientException("优惠券模板异常，请检查操作是否正确...");
        }
        if (ObjectUtil.notEqual(couponTemplateDO.getStatus(),CouponTemplateStatusEnum.ACTIVE.getStatus())){
            throw new ClientException("优惠券模板已结束");
        }
        //3.记录修改前数据
        LogRecordContext.putVariable("orginalData",couponTemplateDO);
        //4.修改发行量
        int  increased=couponTemplateMapper.increaseNumberCouponTemplate(UserContext.getShopNumber(),requestParam.getCouponTemplateId(),requestParam.getNumber());
        //5.增加优惠券模板缓存库存发行量
        String couponTemplateCacheKey=String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY,requestParam.getCouponTemplateId());
        stringRedisTemplate.opsForHash().increment(couponTemplateCacheKey,"stock",requestParam.getNumber());

    }

    /**
     * 分页查询
     * @param requestParam
     * @return
     */
    public IPage<CouponTemplatePageQueryRespDTO> pageQueryCouponTemplate(CouponTemplatePageQueryReqDTO requestParam){
        //构建分页查询模板
        LambdaQueryWrapper<CouponTemplateDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateDO.class)
                .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
                .like(StrUtil.isNotBlank(requestParam.getName()), CouponTemplateDO::getName, requestParam.getName())
                .like(StrUtil.isNotBlank(requestParam.getGoods()), CouponTemplateDO::getGoods, requestParam.getGoods())
                .eq(Objects.nonNull(requestParam.getType()), CouponTemplateDO::getType, requestParam.getType())
                .eq(Objects.nonNull(requestParam.getTarget()), CouponTemplateDO::getTarget, requestParam.getTarget());
        IPage<CouponTemplateDO> selectPage=couponTemplateMapper.selectPage(requestParam,queryWrapper);
        // 转换数据库持久层对象为优惠券模板返回参数
        return selectPage.convert(each -> BeanUtil.toBean(each, CouponTemplatePageQueryRespDTO.class));
    }
    public CouponTemplateQueryRespDTO findCouponTemplateById(String couponTemplateId) {
        LambdaQueryWrapper<CouponTemplateDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateDO.class)
                .eq(CouponTemplateDO::getShopNumber, UserContext.getShopNumber())
                .eq(CouponTemplateDO::getId, couponTemplateId);

        CouponTemplateDO couponTemplateDO = couponTemplateMapper.selectOne(queryWrapper);
        return BeanUtil.toBean(couponTemplateDO, CouponTemplateQueryRespDTO.class);
    }
   /**
    * @return 获取当前线程上下文 ServletPath
    */
   private String getServletPath() {
      ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      return sra.getRequest().getServletPath();
   }

   /**
    * @return 当前操作用户 ID
    */
   private String getCurrentUserId() {
      // 用户属于非核心功能，这里先通过模拟的形式代替。后续如果需要后管展示，会重构该代码
      return "1810518709471555585";
   }

   /**
    * @return joinPoint md5
    */
   private String calcArgsMD5(CouponTemplateSaveReqDTO requestParam) {
      return DigestUtil.md5Hex(JSON.toJSONBytes(requestParam));
   }


}
